package com.gitee.feizns.web.utils;

import com.gitee.feizns.convert.ConvertUtils;
import com.gitee.feizns.web.boot.utils.user.UserTokenHandler;
import com.gitee.feizns.web.boot.utils.user.UserTokenProperties;
import com.gitee.feizns.web.domain.token.Token;
import com.gitee.feizns.web.ex.UnauthorizedException;

import org.springframework.boot.context.properties.EnableConfigurationProperties;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * 关于用户单点登录的工具类
 * @author feizns
 * @since 2019/8/3 0003
 */
@EnableConfigurationProperties(UserTokenProperties.class)
public class UserUtils {

    /**
     * 分隔符
     */
    private static final String SEPARATOR = "-";

    private static List<UserTokenHandler> handler;

    private static UserTokenProperties properties;

    public UserUtils(UserTokenProperties properties, UserTokenHandler... handler) {
        this.handler = Arrays.asList(handler);
        this.properties = properties;
    }

    /**
     * 验证用户有效性的同时获取用户编号
     * @return
     */
    public static String getId() {
        return getToken().getId();
    }

    /**
     * 对token值进行解析
     * 获取用户编号，只验证有效性，不验证过期
     * 如果返回值为null则说明该web环境不具备token
     * @return
     */
    public static String getNullableId() {
        return Optional.ofNullable(getNullableToken()).map(Token::getId).orElse(null);
    }

    /**
     * 获取web环境下指定http头生成的token值（token value）
     * 如果解析失败，则表面当前请求不具备合法的token返回null
     * @return
     */
    public static Token getNullableToken() {
        return parse(WebUtils.getHeader(properties.getHttpHeader()));
    }

    /**
     * 在获取token的过程中对该值进行验证
     * 保证获取到的是一个有效且合法的token
     * @return
     * @throws InvalidTokenException
     * @throws ExpiredTokenException
     */
    public static Token getToken() {
        Token token = getNullableToken();

        //是否有效的token
        if ( token == null )
            throw new InvalidTokenException();

        //已经过期的token
        if ( token.getExpireTime() < System.currentTimeMillis() )
            throw new ExpiredTokenException();

        //自定义验证
        handler.stream().forEach(h -> h.validate(token));

        return token;
    }

    /**
     * 解析token并验证是否是自己私钥生成的
     * 如果分割token失败或是与本地私钥验证失败则返回null
     * @param value
     * @return
     */
    public static Token parse(String value) {
        if ( value != null ) {
            value = EncryptUtils.base64Decode(value);
            String[] val = value.split(SEPARATOR);
            if ( val.length == 3 ) {
                Token token = createFormArray(value, val);

                //判断是否是通过自己的私钥生成的
                if ( generateSignature(token.getId(), token.getExpireTime()).equals(token.getSignature()) )
                    return token;
            }
        }
        return null;
    }

    /**
     * 生成token，会回调token生成时机函数
     * @see UserTokenHandler
     * @param id 用户编号
     * @return
     */
    public static Token generate(Object id) {
        long expireTime = System.currentTimeMillis() + properties.getTimeout();
        Token token = create(id.toString(), expireTime);
        //在生成token的时候对外开放的回调，（可能是一些需要记录生成的token的环境）
        handler.stream().forEach(h -> h.generate(token));
        return token;
    }

    /**
     * 创建一个 token 包含用户编号、过期时间、签名
     * 签名的生成参照生成签名的函数，而token的值不
     * 同于签名，它是用户编号、过期时间、签名三者的
     * 组合值
     * @see UserUtils#generateSignature(String, long)
     * @param id
     * @param expireTime
     * @return
     */
    private static Token create(String id, long expireTime) {
        Token ret = new Token();
        ret.setExpireTime(expireTime);
        ret.setId(id);
        ret.setSignature(generateSignature(id, expireTime));
        ret.setValue(EncryptUtils.base64Encode(ret.getSignature() + SEPARATOR + id + SEPARATOR + expireTime));
        return ret;
    }

    /**
     * 使用服务器私钥加上用户编号加上签名的过期时间
     * 并将三者通过sha1加密来生成token签名
     * @param id
     * @param endTime
     * @return
     */
    private static String generateSignature(String id, long endTime) {
        return EncryptUtils.sha1(properties.getPrivateSecret() + id + endTime);
    }

    /**
     * 把token的值解析为三位数组并进行封装
     * @param original token的值
     * @param arr 将token三者拆分的数组
     * @return
     */
    private static Token createFormArray(String original, String[] arr) {
        Token ret = new Token();
        ret.setSignature(arr[0]);
        ret.setId(arr[1]);
        ret.setExpireTime(ConvertUtils.to(arr[2], Long.class, 0L));
        ret.setValue(original);
        return ret;
    }

    /**
     * 该异常代表该token是无效的
     */
    public static class InvalidTokenException extends UnauthorizedException { }

    /**
     * 该异常代表该token已经过期了
     */
    public static class ExpiredTokenException extends UnauthorizedException { }

}
